package cn.iocoder.yudao.framework.aop;

import lombok.NonNull;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.Assert;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * TODO
 *
 * @className: DynamicDataSourceAdvisor
 * @author: tangkh
 * @date: 2023-09-19 17:48
 */
public class DynamicDataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
    private final Advice advice;
    private final Pointcut pointcut;
    private final Class<? extends Annotation> annotation;
    private final Class<? extends Annotation> annotation2;

    public DynamicDataSourceAdvisor(@NonNull MethodInterceptor advice, @NonNull Class<? extends Annotation> annotation, @NonNull Class<? extends Annotation> annotation2) {
        if (advice == null) {
            throw new NullPointerException("advice is marked non-null but is null");
        } else if (annotation == null) {
            throw new NullPointerException("annotation is marked non-null but is null");
        } else if (annotation2 == null) {
            throw new NullPointerException("annotation2 is marked non-null but is null");
        } else {
            this.advice = advice;
            this.annotation = annotation;
            this.annotation2 = annotation2;
            this.pointcut = this.buildPointcut();
        }
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (this.advice instanceof BeanFactoryAware) {
            ((BeanFactoryAware)this.advice).setBeanFactory(beanFactory);
        }

    }

    private Pointcut buildPointcut() {
        Pointcut cpc = new AnnotationMatchingPointcut(this.annotation2, true);
        Pointcut mpc = new DynamicDataSourceAdvisor.AnnotationMethodPoint(this.annotation2);
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression("execution(* com.cnhis..provider..*Impl.*(..)) or execution(* com.cnhis..provider..*MqConsumer.*(..)) or execution(* com.cnhis.iho.common.mdm.service.impl.*Impl.*(..))");
        return (new ComposablePointcut(aspectJExpressionPointcut.getMethodMatcher())).union(cpc).union(mpc);
    }

    public Pointcut getPointcut() {
        return this.pointcut;
    }

    public Advice getAdvice() {
        return this.advice;
    }

    private static class AnnotationMethodPoint implements Pointcut {
        private final Class<? extends Annotation> annotationType;

        public AnnotationMethodPoint(Class<? extends Annotation> annotationType) {
            Assert.notNull(annotationType, "Annotation type must not be null");
            this.annotationType = annotationType;
        }

        public ClassFilter getClassFilter() {
            return ClassFilter.TRUE;
        }

        public MethodMatcher getMethodMatcher() {
            return new DynamicDataSourceAdvisor.AnnotationMethodPoint.AnnotationMethodMatcher(this.annotationType);
        }

        private static class AnnotationMethodMatcher extends StaticMethodMatcher {
            private final Class<? extends Annotation> annotationType;

            public AnnotationMethodMatcher(Class<? extends Annotation> annotationType) {
                this.annotationType = annotationType;
            }

            public boolean matches(Method method, Class<?> targetClass) {
                if (this.matchesMethod(method)) {
                    return true;
                } else if (Proxy.isProxyClass(targetClass)) {
                    return false;
                } else {
                    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
                    return specificMethod != method && this.matchesMethod(specificMethod);
                }
            }

            private boolean matchesMethod(Method method) {
                return AnnotatedElementUtils.hasAnnotation(method, this.annotationType);
            }
        }
    }
}
